using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AreaController : MonoBehaviour
{
    [Serializable]
    public class LaneData
    {
        public Lane lane;
        public TapBox tapBox;
    }

    public List<LaneData> laneDataList = new();
}
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;

public class AudioManager : MonoBehaviour
{
    public const string APPLAUSE = "applause";
    public const string BAD = "bad";
    public const string OKAY = "okay";
    public const string PERFECT = "perfect";
    public const string HOLD = "hold";
    public const string BUTTON = "button";
    public const string ROOTS = "roots";
    
    public static AudioManager instance;

    public Sound[] sounds = Array.Empty<Sound>();

    private void Awake()
    {
        if (instance && instance != this)
            Destroy(gameObject);
        else
            instance = this;
        for (int i = 0; i < sounds.Length; ++i)
        {
            sounds[i].source = gameObject.AddComponent<AudioSource>();
            sounds[i].source.clip = sounds[i].clip;
            sounds[i].source.pitch = sounds[i].pitch;
            sounds[i].source.loop = sounds[i].loop;
            sounds[i].source.volume = sounds[i].volume;
        }
    }

    [Button]
    public void Play(string _name)
    {
        for (int i = 0; i < sounds.Length; ++i)
        {
            if (sounds[i].clip.name == _name)
            {
                // Debug.Log($"Play {_name}");
                sounds[i].source.Play();
            }
        }
    }

    [Button]
    public void Stop(string _name)
    {
        for (int i = 0; i < sounds.Length; ++i)
        {
            if (sounds[i].clip.name == _name)
            {
                // Debug.Log($"Stop {_name}");
                sounds[i].source.Stop();
            }
        }
    }

    private void OnDestroy()
    {
        instance = null;
    }
}
using System;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;

[CreateAssetMenu(fileName = "New Beat Map", menuName = "Beat Map")]
public class BeatMap : SerializedScriptableObject
{
    //Public Setup Variables
    [BoxGroup("Setup")]
    public AudioClip song;
    
    [BoxGroup("Setup")]
    public int songBpm;
    
    [BoxGroup("Setup")]
    public int hitsPerBeat = 1;
    
    [BoxGroup("Setup")]
    public int lanes = 1;
    
    //Logic Variables
    public float SecPerBeat => 60f / (songBpm * hitsPerBeat);

    [NonSerialized, ShowInInspector, ReadOnly]
    private float totalPossibleScore = 0f;

    [NonSerialized, ShowInInspector, ReadOnly]
    private int totalPossibleBeats = 0;
    
    //Beat Map Variables
    [PropertyOrder(20)]
    [TitleGroup("Beat Map")]
    [TableMatrix(DrawElementMethod = "DrawCell", RowHeight = 20)]
    public NoteType[,] beatMap;

    [PropertyOrder(10)]
    [FoldoutGroup("Editor Functions")]
    [Button]
    public void InitBeatMap()
    {
        if (song == null)
        {
            Debug.LogWarning("Put a song in the beat map first!");
            return;
        }
        float songDuration = song.length;
        int totalBeats = (int)(songDuration / SecPerBeat);
        beatMap = new NoteType[lanes, totalBeats];
    }

    public NoteType GetNoteType(int _lane, int _beat)
    {
        int lowerBound0 = beatMap.GetLowerBound(0);
        int upperBound0 = beatMap.GetUpperBound(0);
        int lowerBound1 = beatMap.GetLowerBound(1);
        int upperBound1 = beatMap.GetUpperBound(1);
        if (_lane < lowerBound0 ||
            _lane > upperBound0 ||
            _beat < lowerBound1 ||
            _beat > upperBound1)
            return NoteType.Empty;
        return beatMap[_lane, _beat];
    }

    public int GetEndTrailBeatLength(int _lane, int _beat)
    {
        NoteType noteType = GetNoteType(_lane, _beat);
        if (noteType != NoteType.Hold)
            return 0;
        int trails = 0;
        //Start from the wave beat and move up
        for (int i = _beat-1; i >= 0; --i)
        {
            NoteType type = GetNoteType(_lane, i);
            if (type == NoteType.HoldTrail)
                trails++;
            else
                break;
        }
        return trails;
    }

#if UNITY_EDITOR
    private static NoteType DrawCell(Rect _rect, NoteType _value)
    {
        if (Event.current.type == EventType.MouseDown && _rect.Contains(Event.current.mousePosition))
        {
            _value = _value switch
            {
                //Change the value of the cell
                NoteType.Empty => NoteType.Tap,
                NoteType.Tap => NoteType.Hold,
                NoteType.Hold => NoteType.HoldTrail,
                NoteType.HoldTrail =>  NoteType.Empty,
                // NoteType.Flick => NoteType.Empty,
                _ => _value
            };
            GUI.changed = true;
            Event.current.Use();
        }
        
        //Pick the Colour according to the note type
        Color colour = _value switch
        {
            NoteType.Empty => StaticHelper.EmptyColour,
            NoteType.Tap => StaticHelper.TapColour,
            NoteType.Hold => StaticHelper.HoldColour,
            NoteType.HoldTrail => StaticHelper.HoldTrailColour,
            NoteType.Flick => StaticHelper.FlickColour,
            _ => StaticHelper.EmptyColour
        };
        EditorGUI.DrawRect(_rect.Padding(1), colour);
        return _value;
    }
#endif

    public float GetBeatPositionInSeconds(int _beat)
    {
        int realBeat = ConvertBeat(_beat);
        return realBeat * SecPerBeat;
    }

    public int ConvertBeat(int _currentBeat)
    {
        float songDuration = song.length;
        int totalBeats = (int)(songDuration / SecPerBeat);
        //Minus 1 because of the array
        int reverseBeat = totalBeats - _currentBeat - 1;
        return reverseBeat;
    }

    public int GetTotalLanes()
    {
        return beatMap.GetUpperBound(0) + 1;
    }

    public int GetTotalBeats()
    {
        return beatMap.GetUpperBound(1) + 1;
    }

    public int GetTotalPossibleCombo()
    {
        return totalPossibleBeats;
    }
    
    public float GetTotalPossibleScore()
    {
        return totalPossibleScore;
    }

    private void OnValidate()
    {
        CalculatePointsAndBeats();
    }

    private void CalculatePointsAndBeats()
    {
        totalPossibleBeats = 0;
        totalPossibleScore = 0f;
        int laneNum = GetTotalLanes();
        int beatNum = GetTotalBeats();
        for (int i = 0; i < laneNum; ++i)
        {
            for (int j = 0; j < beatNum; ++j)
            {
                NoteType noteType = GetNoteType(i, j);
                if (noteType is NoteType.Empty or NoteType.HoldTrail or NoteType.Flick)
                    continue;
                int trailBeatEndLength = GetEndTrailBeatLength(i, j);
                if (trailBeatEndLength > 0)
                {
                    int trailBeatNum = trailBeatEndLength * 2 - 1;
                    totalPossibleBeats += trailBeatNum;
                }
                totalPossibleBeats++;
            }
        }

        float basePoints = 300;
        float incrementAmount = 100;
        int incrementStep = 10;
        int maxIncrements = 10;
        int increments = 0;
        
        for (int i = 0; i < totalPossibleBeats; ++i)
        {
            if (i > 0 && i % incrementStep == 0 && increments < maxIncrements)
                increments++;
            totalPossibleScore += basePoints + incrementAmount * increments;
        }

        //Factor in the perfect modifier
        totalPossibleScore *= 2f;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using Sirenix.OdinInspector;
using UnityEngine;

[CreateAssetMenu(fileName = "New Beat Map Database", menuName = "Beat Map Database")]
public class BeatMapDatabase : SerializedScriptableObject
{
    public List<BeatMap> beatMapList = new();

    private Dictionary<BeatMap, ScoreSaveData> saveDictionary = new();

    public bool TryGetBeatMap(String _name, out BeatMap _beatMap)
    {
        for (int i = 0; i < beatMapList.Count; ++i)
        {
            if (beatMapList[i].name == _name)
            {
                _beatMap = beatMapList[i];
                return true;
            }
        }

        _beatMap = null;
        return false;
    }

    public void LoadSave(BeatMap _beatMap, ScoreSaveData _save)
    {
        saveDictionary.Add(_beatMap, _save);
    }

    public void InsertSave(ScoreSaveData _save)
    {
        if (!TryGetBeatMap(_save.beatMap, out BeatMap beatMap))
            return;
        
        //If there is already save data, check if need to update it
        if (saveDictionary.TryGetValue(beatMap, out ScoreSaveData saveData))
        {
            //If the old data has a higher score, don't try
            if (saveData.score > _save.score)
                return;
            //Else, update the new save dictionary
            saveDictionary[beatMap] = _save;
        }
        else
        {
            saveDictionary.Add(beatMap, _save);
        }
    }

    public bool TryGetSave(BeatMap _beatMap, out ScoreSaveData _saveData)
    {
        return saveDictionary.TryGetValue(_beatMap, out _saveData);
    }

    public bool CanSave(ScoreSaveData _save)
    {
        if (!TryGetBeatMap(_save.beatMap, out BeatMap beatMap))
            return false;
        
        //If there is already save data, check if need to update it
        if (saveDictionary.TryGetValue(beatMap, out ScoreSaveData saveData))
        {
            //If the old data has a higher score, don't try
            return !(saveData.score > _save.score);
        }
        return true;
    }

    public void ClearSave()
    {
        saveDictionary.Clear();
    }
}
#if UNITY_EDITOR
using System;
using System.Collections;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;

public class BeatMapEditor : OdinEditorWindow
{
    [MenuItem("Beat Map/Editor")]
    private static void OpenWindow()
    {
        GetWindow<BeatMapEditor>().Show();
    }

    [TitleGroup("Beat Map")]
    [PropertyOrder(-20)]
    public BeatMap beatMap;

    [TitleGroup("Beat Map")]
    [PropertyOrder(-20)]
    public int beatPlayback = 0;

    [TitleGroup("Heat Map")]
    [OnInspectorGUI("Tick")] 
    [NonSerialized, ShowInInspector, ReadOnly]
    private int currentBeat;

    private GameObject songPlayerObject;
    private SongPlayer songPlayer;
    private bool active = false;

    [PropertyOrder(-10)]
    [HorizontalGroup("Media Buttons")]
    [Button(ButtonSizes.Large), GUIColor(0, 1, 0)]
    public void Play()
    {
        if (active)
            return;
        if (songPlayer == null || songPlayerObject == null)
        {
            songPlayerObject = new("Test Song PLayer");
            songPlayerObject.hideFlags = HideFlags.HideAndDontSave;
            songPlayer = songPlayerObject.AddComponent<SongPlayer>();
        }
        songPlayer.Init(beatMap);
        int beatMapBeat = beatMap.ConvertBeat(beatPlayback);
        Debug.Log("Playing Beat: "+beatMapBeat);
        songPlayer.Play(beatMapBeat);
        active = true;
    }

    [HorizontalGroup("Media Buttons")]
    [Button(ButtonSizes.Large), GUIColor(0f, 0f, 1)]
    public void Pause()
    {
        if (songPlayer == null || songPlayerObject == null)
            return;
        if (songPlayer.paused)
        {
            songPlayer.Resume();
            active = true;
        }
        else
        {
            songPlayer.Pause();
            active = false;
        }
    }

    [HorizontalGroup("Media Buttons")]
    [Button(ButtonSizes.Large), GUIColor(1, 0f, 0)]
    public void Stop()
    {
        if (songPlayer == null || songPlayerObject == null)
            return;
        songPlayer.Stop();
        active = false;
    }
    
    private void Tick()
    {
        if (beatMap == null || songPlayer == null)
            return;
        if (!active)
        {
            currentBeat = 0;
            return;
        }
        songPlayer.Tick();
        currentBeat = songPlayer.CurrentBeat;
        Repaint();
    }
    
    [HorizontalGroup("Lanes")]
    [GUIColor("GetLaneOneColour")]
    [Button("", ButtonSizes.Large)]
    public void LaneOne()
    {
    }
    
    [HorizontalGroup("Lanes")]
    [GUIColor("GetLaneTwoColour")]
    [Button("", ButtonSizes.Large)]
    public void LaneTwo()
    {
    }
    
    [HorizontalGroup("Lanes")]
    [GUIColor("GetLaneThreeColour")]
    [Button("", ButtonSizes.Large)]
    public void LaneThree()
    {
    }
    
    [HorizontalGroup("Lanes")]
    [GUIColor("GetLaneFourColour")]
    [Button("", ButtonSizes.Large)]
    public void LaneFour()
    {
    }
    
    private Color GetLaneOneColour()
    {
        return GetTileColour(0);
    }
    
    private Color GetLaneTwoColour()
    {
        return GetTileColour(1);
    }
    
    private Color GetLaneThreeColour()
    {
        return GetTileColour(2);
    }
    
    private Color GetLaneFourColour()
    {
        return GetTileColour(3);
    }
    
    private Color GetTileColour(int _lane)
    {
        if (beatMap == null || !active)
        {
            return Color.black;
        }

        int beatMapBeat = beatMap.ConvertBeat(currentBeat);
        NoteType note = beatMap.GetNoteType(_lane, beatMapBeat);
        return note switch
        {
            NoteType.Empty => StaticHelper.EmptyColour,
            NoteType.Tap => StaticHelper.TapColour,
            NoteType.Hold => StaticHelper.HoldColour,
            NoteType.Flick => StaticHelper.FlickColour,
            NoteType.HoldTrail => StaticHelper.HoldTrailColour,
            _ => StaticHelper.EmptyColour
        };
    }
}
#endif
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;

public class Character : MonoBehaviour
{
    public Animator animator;
    public AnimationClip idle;
    public AnimationClip tap;
    public AnimationClip hold;
    public AnimationClip lose;

    private void Awake()
    {
        animator.enabled = true;
    }

    [Button]
    public void PlayIdle()
    {
        animator.Play(idle.name);
    }

    [Button]
    public void PlayTap()
    {
        animator.Play(tap.name);
    }

    [Button]
    public void PlayHold()
    {
        animator.Play(hold.name);
    }

    [Button]
    public void PlayLose()
    {
        animator.Play(lose.name);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public class FlickNote : Note
{
    public override void Move(float _deltaTime)
    {
        Transform transform1 = transform;
        Vector3 position = transform1.position;
        position.y -= speed * _deltaTime;
        transform1.position = position;
    }
    
    public override bool CanDespawn()
    {     
        return transform.position.y <= despawnThresholdY;
    }

    public override void RecordHit(float _tapTime)
    {
        base.RecordHit(_tapTime);
    }

    public override void RecordMiss()
    {
        base.RecordMiss();
    }
}
public enum Grade
{
    Bad = 0,
    Okay = 1,
    Perfect = 2
}
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Pool;

public class HoldNote : Note
{
    private class HoldStep
    {
        private readonly float time = 0f;
        private bool flag = false;

        public HoldStep(float _time)
        {
            time = _time;
            flag = false;
        }

        public bool CanCheck(float _songTime)
        {
            if (flag)
                return false;
            if (_songTime >= time)
            {
                flag = true;
                return true;
            }
            return false;
        }

        public bool Checked()
        {
            return flag;
        }
    }

    public SpriteRenderer trailSpriteRenderer;
    public Transform headTransform;
    public Vector3 originalLocalScale;
    public float trailAlpha;
    public Transform trailNoteTransform;
    public const float OFFSET = 0.3f;
    public float scaleMultiplierX;
    public float scaleMultiplierY;
    public float scaleDuration;
    public Ease ease;
    private List<HoldStep> stepList = new();
    private bool heldDown = false;

    private bool playingParticles = false;
    // private bool sequencePlaying = false;

    public override void Init(NotePlayer _notePlayer, Lane _lane, NoteSpawnData _spawnData)
    {
        base.Init(_notePlayer, _lane, _spawnData);
        //Set the position to be halfway between the distance and the wave note
        Vector3 localPosition = trailNoteTransform.localPosition;
        localPosition.y = _spawnData.trailSpawnData.trailDistance * 0.5f;
        trailNoteTransform.localPosition = localPosition;
        
        //Set the scale to be the length of the distance
        Vector3 localScale = trailNoteTransform.localScale;
        //Minus offset so that it will cover up until the end of the next note
        localScale.y = _spawnData.trailSpawnData.trailDistance - OFFSET;
        trailNoteTransform.localScale = localScale;

        //Add all the hold steps
        stepList.Clear();
        float startTime = Position;
        float endTime = _spawnData.trailSpawnData.trailEndPosition;
        endTime -= _spawnData.trailSpawnData.offsetTime;
        int beatLength = _spawnData.trailSpawnData.trailBeatLength;
        float stepGap = (endTime - startTime) / (beatLength * 2);
        //Minus one so that the last step is removed because it is too hard otherwise
        //So if there are 3 beat lengths in the trail, the note potential is 5
        for (int i = 0; i < beatLength * 2 - 1; ++i)
        {
            float stepTime = stepGap * (i + 1);
            stepTime += startTime;
            HoldStep step = new(stepTime);
            stepList.Add(step);
        }

        heldDown = false;
        playingParticles = false;

        // headTransform.localScale = originalLocalScale;
        // headTransform.localPosition = Vector3.zero;
    }

    public override void SetColour(Color _colour)
    {
        base.SetColour(_colour);
        Color colour = _colour;
        colour.a = trailAlpha / 256;
        trailSpriteRenderer.color = colour;
    }

    public override void Move(float _deltaTime)
    {
        Transform transform1 = transform;
        //If at the score threshold, stop if there is a trail. Continue if no trail
        if (ReachedScoreThreshold(transform1))
        {
            if (!TrailNoteReduced())
            {
                //Drop the center point lower and lower
                Vector3 localPosition = trailNoteTransform.localPosition;
                localPosition.y -= speed * _deltaTime * 0.5f;
                trailNoteTransform.localPosition = localPosition;
                
                //Reduce the local scale lower and lower
                Vector3 localScale = trailNoteTransform.localScale;
                localScale.y -= speed * _deltaTime;
                trailNoteTransform.localScale = localScale;
                if (heldDown)
                {
                    if (!playingParticles)
                    {
                        AudioManager.instance.Play(AudioManager.HOLD);
                        lane.PlayHoldParticles();
                        playingParticles = true;
                    }
                }
                else
                {
                    if (playingParticles)
                    {
                        AudioManager.instance.Stop(AudioManager.HOLD);
                        lane.StopHoldParticles();
                        playingParticles = false;
                    }
                }
                //
                // if (heldDown)
                // {
                //     Vector3 headScale = headTransform.localScale;
                //     headScale.x = originalLocalScale.x * scaleMultiplierX;
                //     headScale.y = originalLocalScale.y * scaleMultiplierY;
                //     headTransform.localScale = headScale;
                //     
                //     //Stick it to the score threshold
                //     Vector3 position = headTransform.position;
                //     position.y = scoreThresholdY;
                //     position.y += OFFSET * scaleMultiplierY / 2;
                //     headTransform.position = position;
                // }
                // else
                // {
                //     headTransform.localScale = originalLocalScale;
                //     Vector3 position = headTransform.position;
                //     position.y = scoreThresholdY;
                //     position.y += OFFSET / 2;
                //     headTransform.position = position;
                // }
            }
            else
            {
                MoveDown(transform1, _deltaTime);
            }
        }
        else
        {
            MoveDown(transform1, _deltaTime);
        }
    }

    private void Update()
    {
        CheckHold(notePlayer.songPosition);
    }

    public void CheckHold(float _songTime)
    {
        //CHeck initial miss
        float missTime = Position + scoreController.okayThreshold.Value;
        if (_songTime > missTime)
        {
            //If not missed and not hit, record the miss
            //Should only happen for the head note when it reaches the score threshold
            if (!Missed && !Hit)
            {
                RecordMiss();
                return;
            }
        }
        for (int i = 0; i < stepList.Count; ++i)
        {
            if (!stepList[i].CanCheck(_songTime))
                continue;

            if (heldDown)
                RecordHold();
            else
            {
                //Don't bother with the hit and miss checking. This is for sure miss area
                base.RecordMiss();
                scoreController.RecordMiss();
            }
        }

        // if (heldDown)
        // {
        //     if (!sequencePlaying)
        //     {
        //         // Transform transform1 = headTransform;
        //         // Vector3 originalScale = transform1.localScale;
        //         // Vector3 newLocalScale = originalScale;
        //         // newLocalScale.x *= scaleMultiplierX;
        //         // newLocalScale.y *= scaleMultiplierY;
        //         //
        //         // Sequence sequence = DOTween.Sequence();
        //         // sequence.SetId(10);
        //         // sequence.Append(transform1.DOScale(newLocalScale, scaleDuration)).SetEase(ease).SetLoops(-1, LoopType.Yoyo);
        //         // lane.PlayHoldParticles();
        //         Transform transform1 = headTransform;
        //         Vector3 localScale = transform1.localScale;
        //         localScale.x *= scaleMultiplierX;
        //         localScale.y *= scaleMultiplierY;
        //         transform1.localScale = localScale;
        //         sequencePlaying = true;
        //     }
        // }
        // else
        // {
        //     if (sequencePlaying)
        //     {
        //         headTransform.localScale = originalLocalScale;
        //         DOTween.Kill(10);
        //         lane.StopHoldParticles();
        //         sequencePlaying = false;
        //     }
        // }
    }

    private void MoveDown(Transform _transform, float _deltaTime)
    {
        Vector3 position = _transform.position;
        position.y -= speed * _deltaTime;
        _transform.position = position;
    }
    
    public override bool CanDespawn()
    {
        return ReachedScoreThreshold(transform) && TrailNoteReduced() && AllStepsChecked();
    }

    private bool AllStepsChecked()
    {
        for (int i = 0; i < stepList.Count; ++i)
        {
            if (!stepList[i].Checked())
                return false;
        }

        return true;
    }

    private bool ReachedScoreThreshold(Transform _transform)
    {
        return _transform.position.y <= scoreThresholdY + OFFSET / 2;
    }

    private bool TrailNoteReduced()
    {
        return trailNoteTransform.localScale.y <= 0;
    }

    public override void RecordHit(float _tapTime)
    {
        heldDown = true;
        if (!CanHit)
            return;
        
        base.RecordHit(_tapTime);
        float noteTime = Position;
        float difference = Mathf.Abs(noteTime - _tapTime);

        if (difference <= scoreController.perfectThreshold.Value)
        {
            // AudioManager.instance.Play(AudioManager.PERFECT);
            scoreController.PerfectHit();
        }
        else if(difference <= scoreController.okayThreshold.Value)
        {
            AudioManager.instance.Play(AudioManager.OKAY);
            scoreController.OkayHit();
        }
    }

    private void RecordHold()
    {
        scoreController.PerfectHit();
    }

    public void RecordTapUp()
    {
        heldDown = false;
    }

    public override void RecordMiss()
    {
        if (!Missed && !Hit)
        {
            base.RecordMiss();
            scoreController.RecordMiss();
        }
    }

    public override void UnInit()
    {
        lane.StopHoldParticles();
        base.UnInit();
    }
    
}
using System.Collections;
using System.Collections.Generic;
using UnityAtoms.BaseAtoms;
using UnityEngine;

public class Lane : MonoBehaviour
{
    public bool godMode = false;
    public FloatReference offscreenDistance = new(20.0f);
    public ParticleSystem tapParticles;
    public ParticleSystem holdParticles;
    public bool useGradient = false;
    public Gradient noteGradient;
    public Color noteColour;
    public Color originalColour;
    public Color pressedDownColour;
    public Color deadColour;
    
    public bool IsPlaying { get; set; }
    public float SecondsIntoTrack { get; set; }
    public bool Dead => lives <= 0;

    public SpriteRenderer laneBackground;
    private List<NoteSpawnData> noteSpawnList = new();
    private List<Note> noteList = new();
    private NotePlayer notePlayer;
    private int nextNoteToSpawn_idx = 0;
    private int lives = 0;
    private Character character;

    public void Init(NotePlayer _notePlayer, int _lives)
    {
        notePlayer = _notePlayer;
        lives = _lives;
        ClearNotes();
        laneBackground.color = originalColour;
    }

    public void AssignCharacter(Character _character)
    {
        character = _character;
    }

    private void ClearNotes()
    {
        nextNoteToSpawn_idx = 0;
        noteSpawnList.Clear();
    }

    public void AddNoteSpawnData(NoteSpawnData _spawnData)
    {
        noteSpawnList.Add(_spawnData);
    }

    public void OnPressDown()
    {
        laneBackground.color = pressedDownColour;
        AudioManager.instance.Play(AudioManager.PERFECT);
    }

    public void OnPressUp()
    {
        laneBackground.color = originalColour;
    }
    
    private void Update()
    {
        if (IsPlaying)
        {
            SecondsIntoTrack += Time.deltaTime;

            // check which notes to spawn
            while (nextNoteToSpawn_idx < noteSpawnList.Count)
            {
                float distance = notePlayer.noteSpeed * (noteSpawnList[nextNoteToSpawn_idx].position - SecondsIntoTrack);
                if (distance > offscreenDistance)
                {
                    break;
                }

                Vector3 spawnPosition = notePlayer.scoreThreshold.position;

                // set x to the lane itself and adjust y to the right distance
                Transform transform1 = transform;
                spawnPosition.x = transform1.position.x;
                spawnPosition.y += distance;

                // spawn the note using the note player
                NoteSpawnData spawnData = noteSpawnList[nextNoteToSpawn_idx];
                if (spawnData.CanSpawn())
                {
                    Quaternion worldRotation = transform1.parent.rotation;
                    Note note = notePlayer.InstantiateNote(noteSpawnList[nextNoteToSpawn_idx].noteType, spawnPosition, worldRotation);
                    note.Init(notePlayer, this, spawnData);
                    // Debug.Log($"Assigning {note.name} to {name}");
                    if (useGradient && noteGradient != null)
                    {
                        float random = Random.Range(0f, 1f);
                        Color randomColour = noteGradient.Evaluate(random);
                        note.SetColour(randomColour);
                    }
                    else
                    {
                        note.SetColour(noteColour);
                    }
                    noteList.Add(note);
                }

                // now check the next note
                nextNoteToSpawn_idx++;
            }

            // move the notes
            for (int i = noteList.Count - 1; i >= 0; --i)
            {
                MoveNote(noteList[i]);
            }
        }
    }
    
    private void MoveNote(Note _note)
    {
        _note.Move(Time.deltaTime);
    }

    public void RemoveNote(Note _note)
    {
        noteList.Remove(_note);
        //Need to remove the spawn data of notes that have already passed
        RemoveSpawnData(_note.Beat);
    }

    public void RemoveLife()
    {
        if (godMode)
            return;
        // Debug.Log("Remove Life");
        if (Dead)
            return;
        lives--;
        if (lives <= 0)
        {
            laneBackground.color = deadColour;
            character.PlayLose();
            for (int i = noteList.Count - 1; i >= 0; --i)
            {
                //Remove all active notes from the spawn list before giving it
                //to other lanes
                RemoveSpawnData(noteList[i].Beat);
                Debug.Log($"Removing {noteList[i].name} at {noteList[i].Beat}");
                noteList[i].UnInit();
            }
            notePlayer.ReportLaneDead(this);
            ClearNotes();
        }
    }

    private void RemoveSpawnData(int _beat)
    {
        for (int i = noteSpawnList.Count -1 ; i >= 0; --i)
        {
            if (noteSpawnList[i].beat == _beat)
            {
                noteSpawnList.RemoveAt(i);
                break;
            }
        }
    }

    public void PlayTapParticles()
    {
        if (tapParticles == null)
            return;
        tapParticles.Play();
        if(character != null)
            character.PlayTap();
    }

    public void PlayHoldParticles()
    {
        if (holdParticles == null)
            return;
        holdParticles.Play();
        if(character != null)
            character.PlayHold();
    }

    public void StopHoldParticles()
    {
        if (holdParticles == null)
            return;
        holdParticles.Stop();
        if(character != null)
            character.PlayIdle();
    }

    public List<NoteSpawnData> GetSpawnList()
    {
        return noteSpawnList;
    }

    public bool TryAddSpawnData(NoteSpawnData _spawnData)
    {
        NoteType noteType = _spawnData.noteType;
        //Don't add hold trails, unless processing a regular hold note
        if (noteType == NoteType.HoldTrail)
            return false;
        
        //For tap notes, the trail length is 1, so need to offset by a positive 1
        for (int i = 0; i < _spawnData.trailSpawnData.trailBeatLength + 1; ++i)
        {
            int beatToCheck = _spawnData.beat - i;
            //If there is another note at this beat, then cannot add
            if (IsBeatBusy(beatToCheck))
                return false;
        }
        
        Debug.Log($"{gameObject.name} Adding {noteType} at {_spawnData.beat} with trail {_spawnData.trailSpawnData.trailBeatLength}");
        noteSpawnList.Add(_spawnData);
        for (int i = 0; i < _spawnData.trailSpawnData.trailBeatLength; ++i)
        {
            int beatToInsert = _spawnData.beat - (i+1);
            noteSpawnList.Add(NoteSpawnData.GetHoldTrailData(beatToInsert));
            // Debug.Log($"{gameObject.name} Adding trail at {beatToInsert}");
        }
        
        return true;
    }

    private bool IsBeatBusy(int _beat)
    {
        for (int i = 0; i < noteSpawnList.Count; ++i)
        {
            if (noteSpawnList[i].beat == _beat)
                return true;
        }
        return false;
    }

    public void SortSpawnData()
    {
        SpawnComparer spawnComparer = new();
        noteSpawnList.Sort(spawnComparer);
    }
}

public class SpawnComparer : IComparer<NoteSpawnData>
{
    public int Compare(NoteSpawnData _x, NoteSpawnData _y)
    {
        if (_y != null && _x != null)
        {
            int beatComparison = _y.beat.CompareTo(_x.beat);
            if (beatComparison != 0) return beatComparison;
        }
        return 0;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

public class SaveController : MonoBehaviour
{
    private const string SAVE_PATH = "/Saves/";
    private const string TXT = ".txt";
    public BeatMapDatabase beatMapDatabase;

    private void Awake()
    {
        Init();
    }

    private void Init()
    {
        beatMapDatabase.ClearSave();

        EnsureDirectoryExists();

        Load();
    }

    private void EnsureDirectoryExists()
    {
        if (!Directory.Exists(Application.persistentDataPath + SAVE_PATH))
            Directory.CreateDirectory(Application.persistentDataPath + SAVE_PATH);
    }

    public void Save(ScoreSaveData _save)
    {
        if (beatMapDatabase.CanSave(_save))
        {
            beatMapDatabase.InsertSave(_save);
            string jsonString = JsonUtility.ToJson(_save);
            string file = Application.persistentDataPath + SAVE_PATH + _save.beatMap + TXT;
            File.WriteAllText(file, jsonString);
            Debug.Log($"Saving to {file}\n{jsonString}");
        }
    }

    private void Load()
    {
        //Find existing save files on the device. If there are no save files, then cannot do anything
        for (int i = 0; i < beatMapDatabase.beatMapList.Count; ++i)
        {
            string beatMapName = beatMapDatabase.beatMapList[i].name;
            string file = Application.persistentDataPath + SAVE_PATH + beatMapName + TXT;
            if (!File.Exists(file))
            {
                Debug.Log($"No file found at {file}");
                continue;
            }

            Debug.Log($"Getting data from {file}");
            string saveString = File.ReadAllText(file);
            ScoreSaveData saveData = JsonUtility.FromJson<ScoreSaveData>(saveString);
            beatMapDatabase.LoadSave(beatMapDatabase.beatMapList[i], saveData);
        }
    }

    private void UnInit()
    {
        //Clear temporary save data in scriptable object
        beatMapDatabase.ClearSave();
    }

    private void OnDestroy()
    {
        UnInit();
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityAtoms.BaseAtoms;
using UnityAtoms.SceneMgmt;
using UnityEngine;

[CreateAssetMenu(fileName = "New Score Controller", menuName = "Score Controller")]
public class ScoreController : SerializedScriptableObject
{
    public bool damageMode = false;
    public FloatConstant perfectThreshold;
    public FloatConstant okayThreshold;
    public IntReference perfectHits;
    public IntReference okayHits;
    public IntReference badHits;
    public IntReference combo;
    public FloatReference score;

    [FoldoutGroup("Scoring Parameters")] public float basePoints;
    [FoldoutGroup("Scoring Parameters")] public float incrementAmount;
    
    private Rank rank;
    private int maxCombo;

    public int MaxCombo => maxCombo;

    public void Init()
    {
        perfectHits.Value = 0;
        okayHits.Value = 0;
        badHits.Value = 0;
        combo.Value = 0;
        maxCombo = 0;
        score.Value = 0;
        rank = Rank.C;
    }

    public void RecordMiss(Note _note)
    {
        _note.RecordMiss();
        RecordMiss();
    }

    public void RecordMiss()
    {
        badHits.Value++;
        combo.Value = 0;
        AudioManager.instance.Play(AudioManager.BAD);
    }

    public void PerfectHit()
    {
        perfectHits.Value++;
        combo.Value++;
        UpdateMaxCombo();
        float noteScore = GetNoteScore(Grade.Perfect, combo.Value, NoteType.Tap);
        score.Value += noteScore;
    }
    
    public void OkayHit()
    {
        okayHits.Value++;
        combo.Value++;
        UpdateMaxCombo();
        float noteScore = GetNoteScore(Grade.Okay, combo.Value, NoteType.Tap);
        score.Value += noteScore;
        AudioManager.instance.Play(AudioManager.OKAY);
    }

    private void UpdateMaxCombo()
    {
        if (combo.Value > maxCombo)
            maxCombo = combo.Value;
    }

    public ScoreSaveData ProcessResults(BeatMap _beatMap, bool _cleared)
    {
        float totalPossibleScore = _beatMap.GetTotalPossibleScore();
        float percentage = score.Value / totalPossibleScore;
        rank = percentage switch
        {
            >= 0.85f => Rank.S,
            >= 0.70f => Rank.A,
            >= 0.55f => Rank.B,
            _ => Rank.C
        };
        bool fullCombo = maxCombo == _beatMap.GetTotalPossibleCombo();
        return new(_beatMap.name, score, rank, maxCombo, perfectHits, okayHits, badHits, fullCombo, _cleared);
    }

    public Rank GetRank()
    {
        return rank;
    }

    [Button]
    public float GetNoteScore(Grade _grade, int _combo, NoteType _noteType)
    {
        if (_noteType == NoteType.Empty || _noteType == NoteType.Flick)
            return 0f;
        if (_grade == Grade.Bad)
            return 0f;

        int increments;
        if (_combo <= 10)
            increments = 0;
        else if (_combo <= 20)
            increments = 1;
        else if (_combo <= 30)
            increments = 2;
        else if (_combo <= 40)
            increments = 3;
        else if (_combo <= 50)
            increments = 4;
        else if (_combo <= 60)
            increments = 5;
        else if (_combo <= 70)
            increments = 6;
        else if (_combo <= 80)
            increments = 7;
        else if (_combo <= 90)
            increments = 8;
        else if (_combo <= 100)
            increments = 9;
        else
            increments = 10;
        
        float points = basePoints;
        float multipliedPoints = points + increments * incrementAmount;
        
        float finalPoints = multipliedPoints;
        if (_grade == Grade.Perfect)
            finalPoints *= 2;
        return finalPoints;
    }
    
    public void UnInit()
    {
        perfectHits.Value = 0;
        okayHits.Value = 0;
        badHits.Value = 0;
        combo.Value = 0;
        maxCombo = 0;
        score.Value = 0;
        rank = Rank.C;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class ScoreSaveData
{
    public string beatMap;
    public float score;
    public Rank rank;
    public int maxCombo;
    public int perfect;
    public int okay;
    public int bad;
    public bool fullCombo;
    public bool cleared;

    public void SetId(string _id)
    {
        beatMap = _id;
    }

    public ScoreSaveData(string _beatMap, float _score, Rank _rank, int _maxCombo, int _perfect, int _okay, int _bad, bool _fullCombo, bool _cleared)
    {
        beatMap = _beatMap;
        score = _score;
        rank = _rank;
        maxCombo = _maxCombo;
        perfect = _perfect;
        okay = _okay;
        bad = _bad;
        fullCombo = _fullCombo;
        cleared = _cleared;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityAtoms.BaseAtoms;
using UnityEngine;

public class SongController : MonoBehaviour
{
    [Serializable]
    public class NotePlayerEntry
    {
        public int lanes;
        public NotePlayer notePlayer;
    }

    [Serializable]
    public class CharacterEntry
    {
        public int lane;
        public Character character;
    }

    public SongPlayer songPlayer;
    public ScoreController scoreController;
    public NoteFactory noteFactory;
    public SaveController saveController;

    public BoolReference gamePlaying;

    [TableList] 
    public List<NotePlayerEntry> notePlayerPrefabs = new();
    private List<NotePlayerEntry> notePlayerList = new();
    public List<CharacterEntry> characterList = new();

    private NotePlayer currentNotePlayer;

    private void Awake()
    {
        for (int i = 0; i < notePlayerPrefabs.Count; ++i)
        {
            NotePlayer player = Instantiate(notePlayerPrefabs[i].notePlayer, transform);
            NotePlayerEntry entry = new();
            entry.lanes = notePlayerPrefabs[i].lanes;
            entry.notePlayer = player;
            notePlayerList.Add(entry);
            player.gameObject.SetActiveFast(false);
        }
        gamePlaying.Value = false;
    }

    public void Init(BeatMap _beatMap)
    {
        songPlayer.Init(_beatMap);
        int lanes = _beatMap.lanes;
        NotePlayer notePlayer = GetPlayer(lanes);
        SetCurrentPlayer(notePlayer);
        currentNotePlayer.Init(noteFactory, _beatMap);
        scoreController.Init();

        List<Lane> laneList = currentNotePlayer.laneList;
        for (int i = 0; i < laneList.Count; ++i)
        {
            int laneNumber = i + 1;
            for (int j = 0; j < characterList.Count; ++j)
            {
                if (laneNumber == characterList[i].lane)
                {
                    laneList[i].AssignCharacter(characterList[i].character);
                    break;
                }
            }
        }

        for (int i = 0; i < characterList.Count; ++i)
        {
            characterList[i].character.PlayIdle();
        }
    }

    private void SetCurrentPlayer(NotePlayer _notePlayer)
    {
        if(currentNotePlayer != null)
            currentNotePlayer.gameObject.SetActiveFast(false);
        currentNotePlayer = _notePlayer;
        currentNotePlayer.gameObject.SetActiveFast(true);
    }

    private void Update()
    {
        if (gamePlaying && Application.isFocused)
        {
            //If game is going on, but not audio and no pause, then report game completed
            if (songPlayer.NotPlayingAndNotPaused)
            {
                Debug.Log("Game Completed.");
                gamePlaying.Value = false;
                ScoreSaveData saveData = scoreController.ProcessResults(currentNotePlayer.BeatMap, true);
                if(saveController != null)
                    saveController.Save(saveData);
                if(PopupManager.instance != null)
                    PopupManager.instance.ShowResults(saveData.cleared, saveData.fullCombo);
            }
            else if (currentNotePlayer.IsDead())
            {
                Debug.Log("Game Over.");
                gamePlaying.Value = false;
                ScoreSaveData saveData = scoreController.ProcessResults(currentNotePlayer.BeatMap, false);
                if(saveController != null)
                    saveController.Save(saveData);
                if(PopupManager.instance != null)
                    PopupManager.instance.ShowResults(saveData.cleared, saveData.fullCombo);
            }
        }
    }

    public void Play(int _beat = 0)
    {
        songPlayer.Play(_beat);
        currentNotePlayer.Play(_beat);
        gamePlaying.Value = true;
    }

    public void Pause()
    {
        songPlayer.Pause();
        currentNotePlayer.Pause();
    }

    public void Resume()
    {
        songPlayer.Resume();
        currentNotePlayer.Resume();
    }

    public void Restart()
    {
        Stop();
        Init(currentNotePlayer.BeatMap);
        Play();
    }

    public void Stop()
    {
        songPlayer.Stop();
        currentNotePlayer.Stop();
        scoreController.UnInit();
        gamePlaying.Value = false;

        for (int i = 0; i < characterList.Count; ++i)
        {
            characterList[i].character.PlayIdle();
        }
    }

    private NotePlayer GetPlayer(int _lanes)
    {
        for (int i = 0; i < notePlayerList.Count; ++i)
        {
            if (notePlayerList[i].lanes == _lanes)
                return notePlayerList[i].notePlayer;
        }
        
        Debug.LogWarning("No note player with correct lanes!");
        
        for (int i = 0; i < notePlayerList.Count; ++i)
        {
            if (notePlayerList[i].lanes > _lanes)
                return notePlayerList[i].notePlayer;
        }
        Debug.LogError("No note player with sufficient lanes!");
        return null;
    }

    public void CloseNotePlayer()
    {
        currentNotePlayer.gameObject.SetActiveFast(false);
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using MEC;
using Sirenix.OdinInspector;
using UnityAtoms.BaseAtoms;
using UnityEngine;

public class SongPlayer : MonoBehaviour
{
    private AudioSource audioSource;
    private BeatMap beatMap;
    
    //Current song position, in seconds
    public FloatReference songPosition = new FloatReference();
    
    //Current song position, in beats
    [NonSerialized, ShowInInspector, ReadOnly]
    private float songPositionInBeats = 0f;
    
    //How many seconds have passed since the song started
    [NonSerialized, ShowInInspector, ReadOnly]
    private float dspSongTime = 0f;
    
    //The current beat of the song
    [NonSerialized, ShowInInspector, ReadOnly]
    private int currentBeat = 0;
    private int previousBeat = 0;
    private int beatOffset = 0;
    private bool playing = false;
    
    //Pause Variables
    private float pauseOffset = 0f;
    private float pausedTimeStamp = 0f;
    public bool paused = false;

    public int CurrentBeat => currentBeat;
    public float SongPositionInBeats => songPositionInBeats;
    public bool NotPlayingAndNotPaused => !audioSource.isPlaying && !paused;

    [FoldoutGroup("Editor Functions")]
    [Button]
    public void Init(BeatMap _beatMap)
    {
        if(audioSource == null)
            audioSource = gameObject.AddComponent<AudioSource>();
        beatMap = _beatMap;
        audioSource.clip = beatMap.song;
    }

    private void Update()
    {
        Tick();
    }

    [FoldoutGroup("Editor Functions")]
    [Button]
    public void Play(int _beat = 0)
    {
        if (beatMap == null)
        {
            Debug.LogError("No beat map attached to this song player.");
            return;
        }
        float songTime = _beat * beatMap.SecPerBeat;
        audioSource.time = songTime;
        audioSource.Play();
        beatOffset = _beat;
        
        //Record the time when the music starts
        dspSongTime = (float)AudioSettings.dspTime;
        playing = true;
        paused = false;
        
        //Set the paused time to 0
        pauseOffset = 0;
    }
    
    public void Tick()
    {
        if (!playing)
            return;
        //determine how many seconds since the song started
        songPosition.Value = (float)(AudioSettings.dspTime - dspSongTime);
        
        //If the song has been paused at any time, minus the offset calculated in the pause
        songPosition.Value -= pauseOffset;

        //determine how many beats since the song started
        songPositionInBeats = songPosition / beatMap.SecPerBeat;
        songPositionInBeats += beatOffset;
        
        currentBeat = (int)(songPositionInBeats);
        if (currentBeat > previousBeat)
        {
            previousBeat = currentBeat;
        }
    }

    [FoldoutGroup("Editor Functions")]
    [Button]
    public void Pause()
    {
        audioSource.Pause();
        playing = false;
        pausedTimeStamp = (float)(AudioSettings.dspTime);
        paused = true;
    }
    
    [FoldoutGroup("Editor Functions")]
    [Button]
    public void Resume()
    {
        audioSource.Play();
        playing = true;
        float totalElapsedPauseTime = (float)(AudioSettings.dspTime - pausedTimeStamp);
        pauseOffset += totalElapsedPauseTime;
        paused = false;
    }

    [FoldoutGroup("Editor Functions")]
    [Button]
    public void Stop()
    {
        audioSource.Stop();
        playing = false;
        paused = false;
    }
}
using System;
using UnityEngine;

[Serializable]
public class Sound
{
    public AudioClip clip;
    public bool loop = false;
    [Range(0f, 1f)] public float volume = 1f;
    [Range(.1f, 3f)] public float pitch = 1f;
    [HideInInspector] public AudioSource source;
}
using System.Collections.Generic;
using UnityEngine;
using Random = System.Random;

public static class StaticHelper
{
    public static readonly Color EmptyColour = new Color(0, 0, 0, 0.5f);
    public static readonly Color TapColour = new Color32(124, 255, 175, 252);
    public static readonly Color HoldColour = new Color32(113, 207, 232, 252);
    public static readonly Color HoldTrailColour = new Color32(113, 207, 232, 100);
    public static readonly Color FlickColour = new Color32(218, 155, 232, 252);
    
    public static void AddOnce<T>(this HashSet<T> _list, T _item)
    {
        if (_item != null && !_list.Contains(_item))
            _list.Add(_item);
    }
    
    public static void AddOnce<T>(this List<T> _list, T _item)
    {
        if (_item != null && !_list.Contains(_item))
            _list.Add(_item);
    }
    
    public static void SetActiveFast(this GameObject _gameObject, bool _active)
    {
        if (_gameObject.activeSelf == _active)
            return;

        _gameObject.SetActive(_active);
    }

    public static List<T> RandomSubList<T>(this Random _rng, List<T> _list, int _maxElements)
    {
        if (_list.Count <= 0)
            return new List<T>(_list);
        if (_list.Count < _maxElements)
            return _list;
        List<T> list = new();
        List<int> indexList = new();
        for (int i = 0; i < _maxElements; ++i)
        {
            int k = _rng.Next(_list.Count);
            while(indexList.Contains(k))
                k = _rng.Next(_list.Count);
            indexList.Add(k);
            list.Add(_list[k]);
        }
        return list;
    }

    public static void Shuffle<T> (this Random _rng, T[] _array)
    {
        int n = _array.Length;
        while (n > 1) 
        {
            int k = _rng.Next(n--);
            (_array[n], _array[k]) = (_array[k], _array[n]);
        }
    }
    
    public static void Shuffle<T> (this Random _rng, List<T> _list)
    {
        int n = _list.Count;
        while (n > 1) 
        {
            int k = _rng.Next(n--);
            (_list[n], _list[k]) = (_list[k], _list[n]);
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TapBox : MonoBehaviour
{
    public Lane lane;
    public ScoreController scoreController;
    public float localScaleSizeMultiplier = 0.8f;
    private Collider2D[] hitColliders = new Collider2D[10];
    public bool tutorial = false;

    public void Init(ScoreController _scoreController)
    {
        scoreController = _scoreController;
    }
    
    public void TapDown(float _time)
    {
        // Debug.Log("Tapping");
        if (tutorial)
        {
            lane.OnPressDown();
            return;
        }
        if(lane != null && !lane.Dead)
            lane.OnPressDown();
        Transform transform1 = transform;
        int size = Physics2D.OverlapBoxNonAlloc(transform1.position, transform1.localScale * localScaleSizeMultiplier, 0f, hitColliders);
        for (int i = 0; i < size; ++i)
        {
            if (hitColliders[i] == null)
                continue;
            
            if(hitColliders[i].gameObject.TryGetComponent(out TapNote tapNote))
            {
                tapNote.RecordHit(_time);
            }
            else if (hitColliders[i].gameObject.TryGetComponent(out HoldNote holdNote))
            {
                holdNote.RecordHit(_time);
            }
        }
    }

    public void TapUp()
    {
        if (tutorial)
        {
            lane.OnPressUp();
            return;
        }
        if(lane != null && !lane.Dead)
            lane.OnPressUp();
        Transform transform1 = transform;
        int size = Physics2D.OverlapBoxNonAlloc(transform1.position, transform1.localScale * localScaleSizeMultiplier, 0f, hitColliders);
        for (int i = 0; i < size; ++i)
        {
            if (hitColliders[i] == null)
                continue;
            if (hitColliders[i].gameObject.TryGetComponent(out HoldNote holdNote))
            {
                holdNote.RecordTapUp();
            }
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Vector3 position = gameObject.transform.position;
        Gizmos.DrawWireCube(position, transform.localScale * localScaleSizeMultiplier);
        // to visualize t$$anonymous$$s:
        // Physics.OverlapBox(pos, scale, rotation, ...)
    }
}
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Pool;
using Vector3 = UnityEngine.Vector3;

public class TapNote : Note
{
    public float scaleMultiplierX;
    public float scaleMultiplierY;
    public float scaleDuration;
    public Ease ease;
    
    public override void Move(float _deltaTime)
    {
        if (Hit)
        {
            //Stick it to the score threshold
            Transform transform1 = transform;
            Vector3 position = transform1.position;
            position.y = scoreThresholdY;
            position.y += HoldNote.OFFSET * scaleMultiplierY / 2;
            transform1.position = position;
        }
        else
        {
            Transform transform1 = transform;
            Vector3 position = transform1.position;
            position.y -= speed * _deltaTime;
            transform1.position = position;
        }
    }
    
    public override bool CanDespawn()
    {     
        return transform.position.y <= despawnThresholdY;
    }

    public override void RecordHit(float _tapTime)
    {
        if (!CanHit)
            return;
        base.RecordHit(_tapTime);
        float noteTime = Position;
        float difference = Mathf.Abs(noteTime - _tapTime);

        if (difference <= scoreController.perfectThreshold.Value)
        {
            scoreController.PerfectHit();
            // AudioManager.instance.Play(AudioManager.PERFECT);
        }
        else if(difference <= scoreController.okayThreshold.Value)
        {
            AudioManager.instance.Play(AudioManager.OKAY);
            scoreController.OkayHit();
        }
        
        Sequence sequence = DOTween.Sequence();
        Transform transform1 = transform;
        Vector3 originalLocalScale = transform1.localScale;
        Vector3 newLocalScale = originalLocalScale;
        newLocalScale.x *= scaleMultiplierX;
        newLocalScale.y *= scaleMultiplierY;
        sequence.Append(transform1.DOScale(newLocalScale, scaleDuration)).SetEase(ease);
        sequence.OnComplete(() =>
        {
            lane.PlayTapParticles();
            base.UnInit();
            transform1.localScale = originalLocalScale;
        });
    }

    public override void RecordMiss()
    {
        base.RecordMiss();
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using Sirenix.OdinInspector;
using TMPro;
using UnityAtoms.BaseAtoms;
using UnityEngine;
using UnityEngine.UI;

public class TestingPopup : Popup
{
    [FoldoutGroup("System Objects")] public BeatMapDatabase beatMapDatabase;
    [FoldoutGroup("System Objects")] public SongController songController;
    [FoldoutGroup("System Objects")] public IntReference combo;
    [FoldoutGroup("System Objects")] public IntReference perfectHits;
    [FoldoutGroup("System Objects")] public IntReference okayHits;
    [FoldoutGroup("System Objects")] public IntReference badHits;
    
    [FoldoutGroup("UI Objects")] public TMP_Dropdown dropdown;
    [FoldoutGroup("UI Objects")] public TMP_InputField beatInputField;
    [FoldoutGroup("UI Objects")] public Button playButton;
    [FoldoutGroup("UI Objects")] public Button pauseButton;
    [FoldoutGroup("UI Objects")] public Button resumeButton;
    [FoldoutGroup("UI Objects")] public Button stopButton;
    [FoldoutGroup("UI Objects")] public TMP_Text beatText;
    [FoldoutGroup("UI Objects")] public TMP_Text descriptionText;

    protected override void InitPopup()
    {
        dropdown.ClearOptions();
        List<TMP_Dropdown.OptionData> optionList = new();
        for (int i = 0; i < beatMapDatabase.beatMapList.Count; ++i)
        {
            String songName = beatMapDatabase.beatMapList[i].name;
            TMP_Dropdown.OptionData optionData = new TMP_Dropdown.OptionData();
            optionData.text = songName;
            optionList.Add(optionData);
        }
        dropdown.AddOptions(optionList);
        ShowPopup();
    }

    public override void ShowPopup()
    {
        gameObject.SetActiveFast(true);
    }

    public override void HidePopup()
    {
        gameObject.SetActiveFast(false);
    }

    private void Update()
    {
        beatText.text = "Beat: " + songController.songPlayer.SongPositionInBeats;
    }

    public void Play()
    {
        String beatTextString = beatInputField.text;
        if (String.IsNullOrEmpty(beatTextString))
            PlaySong(-1);
        else if (int.TryParse(beatTextString, out int beat))
            PlaySong(beat);
        else
            Debug.LogError("Please input a beat first");
    }

    private void PlaySong(int _beat)
    {
        String beatMapName = dropdown.options[dropdown.value].text;
        if (!beatMapDatabase.TryGetBeatMap(beatMapName, out BeatMap beatMap))
            return;

        songController.Init(beatMap);
        if (_beat < 0)
            songController.Play();
        else
        {
            int beatMapBeat = beatMap.ConvertBeat(_beat);
            songController.Play(beatMapBeat);
        }
        dropdown.gameObject.SetActiveFast(false);
        playButton.gameObject.SetActiveFast(false);
        pauseButton.gameObject.SetActiveFast(true);
        stopButton.gameObject.SetActiveFast(true);
    }

    public void Pause()
    {
        songController.Pause();
        pauseButton.gameObject.SetActiveFast(false);
        resumeButton.gameObject.SetActiveFast(true);
    }
    
    public void Resume()
    {
        songController.Resume();
        pauseButton.gameObject.SetActiveFast(true);
        resumeButton.gameObject.SetActiveFast(false);
    }

    public void UpdateDescription()
    {
        descriptionText.text = $"Combo: {combo.Value}, Perfect: {perfectHits.Value}, Okay: {okayHits.Value}, Bad: {badHits.Value}";
    }
    
    public void Stop()
    {
        songController.Stop();
        dropdown.gameObject.SetActiveFast(true);
        playButton.gameObject.SetActiveFast(true);
        stopButton.gameObject.SetActiveFast(false);
        pauseButton.gameObject.SetActiveFast(false);
        resumeButton.gameObject.SetActiveFast(false);
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;

public class MainMenuPopup : Popup
{
    [FoldoutGroup("UI Objects")] public TitlePopup titlePopup;
    [FoldoutGroup("UI Objects")] public SongSelectionPopup songSelectionPopup;
    [FoldoutGroup("UI Objects")] public CreditsPopup creditsPopup;
    [FoldoutGroup("UI Objects")] public TutorialPopup tutorialPopup;

    protected override void InitPopup()
    {
        ShowPopup();
    }

    public override void ShowPopup()
    {
        songSelectionPopup.HidePopup();
        titlePopup.ShowPopup();
    }

    public void PlayButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        ShowSongSelection();
    }

    public void CreditsButtonClicked()
    {
        titlePopup.HidePopup();
        creditsPopup.ShowPopup();
    }

    public void TutorialButtonClicked()
    {
        titlePopup.HidePopup();
        tutorialPopup.ShowPopup();
    }

    public void BackButtonClicked()
    {
        if (songSelectionPopup.isShowing)
        {
            AudioManager.instance.Play(AudioManager.BUTTON);
            songSelectionPopup.HidePopup();
            titlePopup.ShowPopup();
        }

        else if (creditsPopup.isShowing)
        {
            creditsPopup.HidePopup();
            titlePopup.ShowPopup();
        }
        else if (tutorialPopup.isShowing)
        {
            tutorialPopup.HidePopup();
            titlePopup.ShowPopup();
        }
    }

    public void ShowSongSelection()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        titlePopup.HidePopup();
        songSelectionPopup.ShowPopup();
    }

    public override void HidePopup()
    {
        throw new NotImplementedException();
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityAtoms.BaseAtoms;
using UnityEngine;
using UnityEngine.InputSystem;

public class PausePopup : Popup
{
    [FoldoutGroup("System Objects")] public BoolVariable gamePlaying;
    
    [FoldoutGroup("UI Objects")] public RectTransform mainHolder;
    [FoldoutGroup("UI Objects")] public RectTransform mediaHolder;
    [FoldoutGroup("UI Objects")] public CountdownPopupItem countdownItem;
    

    private PlayerControls playerControls;
    private bool countdownActive = false;
    
    protected override void InitPopup()
    {
        playerControls = new PlayerControls();
        playerControls.Player.Pause.started += PauseStarted;
        playerControls.Enable();
        mainHolder.gameObject.SetActiveFast(false);
        HidePopup();
    }

    private void PauseStarted(InputAction.CallbackContext _callbackContext)
    {
        PauseButtonClicked();
    }

    public override void ShowPopup()
    {
        gameObject.SetActiveFast(true);
        mainHolder.gameObject.SetActiveFast(true);
        mediaHolder.gameObject.SetActiveFast(true);
        countdownItem.gameObject.SetActiveFast(false);
        base.ShowPopup();
    }
    
    public void PauseButtonClicked()
    {
        if (!gamePlaying.Value)
            return;
        if (countdownActive)
            return;
        
        if (isShowing)
        {
            ResumeButtonClicked();
        }
        else
        {
            PopupManager.instance.PauseButtonClicked();
            ShowPopup();
        }
    }

    public void SettingsButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
    }

    public void ResumeButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        mediaHolder.gameObject.SetActiveFast(false);
        countdownActive = true;
        countdownItem.Activate(() =>
        {
            countdownActive = false;
            PopupManager.instance.ResumeButtonClicked();
            mainHolder.gameObject.SetActiveFast(false);
            HidePopup();
        });
    }

    public void RestartButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        PopupManager.instance.RestartButtonClicked();
        mainHolder.gameObject.SetActiveFast(false);
        HidePopup();
    }

    public void ExitButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        PopupManager.instance.ExitButtonClicked();
        mainHolder.gameObject.SetActiveFast(false);
        HidePopup();
    }

    public override void HidePopup()
    {
        base.HidePopup();
    }

    private void OnDestroy()
    {
        playerControls.Player.Pause.started -= PauseStarted;
        playerControls.Disable();
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;

public class PopupManager : MonoBehaviour
{
    public static PopupManager instance;

    [FoldoutGroup("System Objects")] public SongController songController;
    [FoldoutGroup("System Objects")] public TutorialObject tutorialObject;
    
    [FoldoutGroup("UI Objects")] public MainMenuPopup mainMenuPopup;
    [FoldoutGroup("UI Objects")] public PausePopup pausePopup;
    [FoldoutGroup("UI Objects")] public GamePopup gamePopup;
    [FoldoutGroup("UI Objects")] public ResultsPopup resultPopup;
    
    private void Awake()
    {
        if (instance && instance != this)
            Destroy(gameObject);
        else
            instance = this;
    }

    private void Start()
    {
        AudioManager.instance.Play(AudioManager.ROOTS);
    }

    public void PlaySong(BeatMap _beatMap)
    {
        AudioManager.instance.Stop(AudioManager.ROOTS);
        songController.Init(_beatMap);
        songController.Play();
        gamePopup.ShowPopup();
    }

    public void PauseButtonPressed()
    {
        pausePopup.PauseButtonClicked();
    }

    public void PauseButtonClicked()
    {
        songController.Pause();
    }

    public void ResumeButtonClicked()
    {
        songController.Resume();
    }
    
    public void RestartButtonClicked()
    {
        songController.Restart();
        gamePopup.ShowPopup();
    }

    public void ShowTutorial()
    {
        tutorialObject.Show();
    }

    public void HideTutorial()
    {
        tutorialObject.Hide();
    }

    public void ExitButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.ROOTS);
        songController.Stop();
        songController.CloseNotePlayer();
        gamePopup.HidePopup();
        mainMenuPopup.ShowSongSelection();
    }

    public void ShowResults(bool _cleared, bool _maxCombo)
    {
        gamePopup.HidePopup();
        resultPopup.SetClearedAndFullCombo(_cleared, _maxCombo);
        resultPopup.ShowPopup();
    }

    private void OnDestroy()
    {
        instance = null;
    }

    public void PlayButtonSound()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
    }
}
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using Sirenix.OdinInspector;
using TMPro;
using UnityEngine;

public class ResultsPopup : Popup
{
    [FoldoutGroup("System Objects")] public ScoreController scoreController;
    
    [FoldoutGroup("UI Objects")] public RectTransform contentHolder;
    [FoldoutGroup("UI Objects")] public RectTransform sRankHolder;
    [FoldoutGroup("UI Objects")] public RectTransform aRankHolder;
    [FoldoutGroup("UI Objects")] public RectTransform bRankHolder;
    [FoldoutGroup("UI Objects")] public RectTransform cRankHolder;
    [FoldoutGroup("UI Objects")] public RectTransform notClearedHolder;
    [FoldoutGroup("UI Objects")] public TMP_Text scoreText;
    [FoldoutGroup("UI Objects")] public TMP_Text maxComboText;
    [FoldoutGroup("UI Objects")] public TMP_Text perfectText;
    [FoldoutGroup("UI Objects")] public TMP_Text okayText;
    [FoldoutGroup("UI Objects")] public TMP_Text badText;
    [FoldoutGroup("UI Objects")] public TMP_Text fullComboText;

    private bool cleared = false;
    private bool fullCombo = false;
    
    protected override void InitPopup()
    {
        contentHolder.gameObject.SetActiveFast(false);
        HidePopup();
    }

    public override void ShowPopup()
    {
        base.ShowPopup();
        contentHolder.gameObject.SetActiveFast(true);
        UpdateRank();
        UpdateStats();
        if (cleared)
        {
            AudioManager.instance.Play(AudioManager.APPLAUSE);
        }
    }

    public void SetClearedAndFullCombo(bool _cleared, bool _fullCombo)
    {
        cleared = _cleared;
        fullCombo = _fullCombo;
    }

    private void UpdateRank()
    {
        Rank rank = scoreController.GetRank();
        sRankHolder.gameObject.SetActiveFast(rank == Rank.S && cleared);
        aRankHolder.gameObject.SetActiveFast(rank == Rank.A && cleared);
        bRankHolder.gameObject.SetActiveFast(rank == Rank.B && cleared);
        cRankHolder.gameObject.SetActiveFast(rank == Rank.C && cleared);
        notClearedHolder.gameObject.SetActiveFast(!cleared);
    }

    private void UpdateStats()
    {
        scoreText.text = Mathf.RoundToInt(scoreController.score.Value).ToString();
        maxComboText.text = scoreController.MaxCombo.ToString();
        perfectText.text = scoreController.perfectHits.Value.ToString();
        okayText.text = scoreController.okayHits.Value.ToString();
        badText.text = scoreController.badHits.Value.ToString();
        fullComboText.gameObject.SetActiveFast(fullCombo);
    }

    public void RestartButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        PopupManager.instance.RestartButtonClicked();
        contentHolder.gameObject.SetActiveFast(false);
        HidePopup();
        AudioManager.instance.Stop(AudioManager.APPLAUSE);
    }

    public void ExitButtonClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        PopupManager.instance.ExitButtonClicked();
        contentHolder.gameObject.SetActiveFast(false);
        HidePopup();
        AudioManager.instance.Stop(AudioManager.APPLAUSE);
    }
    
    public override void HidePopup()
    {
        base.HidePopup();
    }
}
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using TMPro;
using UnityEngine;

public class SelectionDetailPopupItem : MonoBehaviour
{
    [FoldoutGroup("UI Objects")] public TMP_Text beatMapText;
    [FoldoutGroup("UI Objects")] public RectTransform recordContent;
    [FoldoutGroup("UI Objects")] public RectTransform noSaveDataContent;
    [FoldoutGroup("UI Objects")] public TMP_Text notClearedText;
    [FoldoutGroup("UI Objects")] public TMP_Text fullComboText;
    [FoldoutGroup("UI Objects")] public TMP_Text rankText;
    [FoldoutGroup("UI Objects")] public TMP_Text scoreText;
    [FoldoutGroup("UI Objects")] public TMP_Text maxComboText;
    [FoldoutGroup("UI Objects")] public TMP_Text perfectText;
    [FoldoutGroup("UI Objects")] public TMP_Text okayText;
    [FoldoutGroup("UI Objects")] public TMP_Text badText;
    
    private UIRecord record;

    public void Init(UIRecord _record)
    {
        record = _record;
    }

    public void Show()
    {
        gameObject.SetActiveFast(true);
        if (record.rank == Rank.Unranked)
        {
            recordContent.gameObject.SetActiveFast(false);
            noSaveDataContent.gameObject.SetActiveFast(true);
            beatMapText.text = record.beatMap;
        }
        else
        {
            recordContent.gameObject.SetActiveFast(true);
            noSaveDataContent.gameObject.SetActiveFast(false);
            beatMapText.text = record.beatMap;
            if (record.cleared)
            {
                rankText.gameObject.SetActiveFast(true);
                rankText.text = record.rank.ToString();
                notClearedText.gameObject.SetActiveFast(false);
            }
            else
            {
                rankText.gameObject.SetActiveFast(false);
                notClearedText.gameObject.SetActiveFast(true);
            }
            fullComboText.gameObject.SetActiveFast(record.fullCombo);
            scoreText.text = Mathf.RoundToInt(record.score).ToString();
            maxComboText.text = record.maxCombo.ToString();
            perfectText.text = record.perfect.ToString();
            okayText.text = record.okay.ToString();
            badText.text = record.bad.ToString();
        }
    }

    public void Hide()
    {
        gameObject.SetActiveFast(false);
    }
}
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;

public class SelectionPopupItem : MonoBehaviour
{
    [FoldoutGroup("UI Objects")] public TMP_Text title;
    [FoldoutGroup("UI Objects")] public SongSelectionPopup songSelectionPopup;

    private BeatMap beatMap;

    public void Init(BeatMap _beatMap)
    {
        beatMap = _beatMap;
        title.text = beatMap.name;
    }
    
    public void OnPointerEnter(BaseEventData _eventData)
    {
        songSelectionPopup.ShowDetail(beatMap);
    }

    public void OnPointerExit(BaseEventData _eventData)
    {
        songSelectionPopup.HideDetail();
    }
    
    public void SongClicked()
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        PopupManager.instance.PlaySong(beatMap);
    }
}
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;

public class SongSelectionPopup : Popup
{
    [FoldoutGroup("System Objects")] public BeatMapDatabase beatMapDatabase;
    
    [FoldoutGroup("UI Objects")] public RectTransform selectionHolder;
    [FoldoutGroup("UI Objects")] public SelectionPopupItem selectionPopupItemSample;
    [FoldoutGroup("UI Objects")] public SelectionDetailPopupItem detailPopupItem;

    private List<SelectionPopupItem> selectionList = new();

    protected override void InitPopup()
    {
        int numToSpawn = beatMapDatabase.beatMapList.Count - selectionList.Count;
        if (numToSpawn > 0)
        {
            selectionPopupItemSample.gameObject.SetActiveFast(true);
            for (int i = 0; i < numToSpawn; ++i)
            {
                SelectionPopupItem popupItem = Instantiate(selectionPopupItemSample, selectionHolder);
                selectionList.Add(popupItem);
            }
        }
        selectionPopupItemSample.gameObject.SetActiveFast(false);
    }

    public override void ShowPopup()
    {
        gameObject.SetActiveFast(true);
        for (int i = 0; i < selectionList.Count; ++i)
        {
            if (i < beatMapDatabase.beatMapList.Count)
            {
                selectionList[i].gameObject.SetActiveFast(true);
                selectionList[i].Init(beatMapDatabase.beatMapList[i]);
            }
            else
            {
                selectionList[i].gameObject.SetActiveFast(false);
            }
        }
        detailPopupItem.gameObject.SetActiveFast(false);
        base.ShowPopup();
    }

    public void ShowDetail(BeatMap _beatMap)
    {
        AudioManager.instance.Play(AudioManager.BUTTON);
        if(beatMapDatabase.TryGetSave(_beatMap, out ScoreSaveData saveData))
        {
            UIRecord record = new (saveData);
            detailPopupItem.Init(record);
            detailPopupItem.Show();
        }
        else
        {
            UIRecord record = new(_beatMap.name);
            detailPopupItem.Init(record);
            detailPopupItem.Show();
        }
    }

    public void HideDetail()
    {
        detailPopupItem.Hide();
    }
    
    public override void HidePopup()
    {
        gameObject.SetActiveFast(false);
        base.HidePopup();
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using MEC;
using Sirenix.OdinInspector;
using UnityEngine;

public class TitlePopup : Popup
{
    [FoldoutGroup("UI Objects")] public RectTransform[] buttonTransforms = Array.Empty<RectTransform>();
    [FoldoutGroup("UI Objects")] public RectTransform logoTransform;
    [FoldoutGroup("Parameters")] public float offsetX = 0f;
    [FoldoutGroup("Parameters")] public float targetX = 0f;
    [FoldoutGroup("Parameters")] public float slideDuration = 0f;
    [FoldoutGroup("Parameters")] public float slideInterval = 0f;
    [FoldoutGroup("Parameters")] public Vector3 smallScale;
    // [FoldoutGroup("Parameters")] public float scaleDuration;
    [FoldoutGroup("Parameters")] public Ease ease;

    protected override void InitPopup()
    {
        ResetButtons();
    }

    [Button]
    public override void ShowPopup()
    {
        gameObject.SetActiveFast(true);
        ResetButtons();
        Timing.RunCoroutine(SlideButtons());
        logoTransform.localScale = smallScale;
        logoTransform.DOScale(Vector3.one, slideDuration).SetEase(ease);
        base.ShowPopup();
    }

    private void ResetButtons()
    {
        for (int i = 0; i < buttonTransforms.Length; ++i)
        {
            RectTransform button = buttonTransforms[i];
            Vector3 position = button.position;
            position.x = offsetX;
            button.position = position;
        }
    }

    private IEnumerator<float> SlideButtons()
    {
        int i = 0;
        while (i < buttonTransforms.Length)
        {
            buttonTransforms[i].DOMoveX(targetX, slideDuration).SetEase(Ease.InOutExpo);
            ++i;
            yield return Timing.WaitForSeconds(slideInterval);
        }
    }

    public override void HidePopup()
    {
        Timing.KillCoroutines();
        gameObject.SetActiveFast(false);
        base.HidePopup();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TutorialPopup : Popup
{
    public RectTransform contentHolder;
    protected override void InitPopup()
    {
        
    }

    public override void ShowPopup()
    {
        PopupManager.instance.ShowTutorial();
        contentHolder.gameObject.SetActiveFast(true);
        base.ShowPopup();
    }

    public override void HidePopup()
    {
        PopupManager.instance.HideTutorial();
        contentHolder.gameObject.SetActiveFast(false);
        base.HidePopup();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public struct UIRecord
{
    public string beatMap;
    public float score;
    public Rank rank;
    public int maxCombo;
    public int perfect;
    public int okay;
    public int bad;
    public bool cleared;
    public bool fullCombo;

    public UIRecord(string _beatMap)
    {
        beatMap = _beatMap;
        score = 0;
        rank = Rank.Unranked;
        maxCombo = 0;
        perfect = 0;
        okay = 0;
        bad = 0;
        cleared = false;
        fullCombo = false;
    }

    public UIRecord(ScoreSaveData _saveData)
    {
        beatMap = _saveData.beatMap;
        score = _saveData.score;
        rank = _saveData.rank;
        maxCombo = _saveData.maxCombo;
        perfect = _saveData.perfect;
        okay = _saveData.okay;
        bad = _saveData.bad;
        cleared = _saveData.cleared;
        fullCombo = _saveData.fullCombo;
    }
}
